using System;
using System.Web;
using System.IO;
using System.Web.Configuration;
using System.Configuration;
using System.Text.RegularExpressions;
using System.Security.Principal;
using System.Text;


namespace NTFSBypassModule
{
    public class NTFSBypassModule : IHttpModule
    {
        private Configuration _WebConfigurationManager;
        public void Init(HttpApplication app)
        {
            // Atach to the Authenticat Request event. This occurs imeaditley after authentication, but before authorization
            app.AuthenticateRequest += new EventHandler(app_AuthenticateRequest);
        }

        void app_AuthenticateRequest(object sender, EventArgs e)
        {
            HttpApplication httpApplication = (HttpApplication)sender;
            HttpResponse Response = HttpContext.Current.Response;
            HttpRequest Request = HttpContext.Current.Request;

            // Only handle GET and HEAD requests differently, let IIS take care of everything else
            if (!(Request.HttpMethod == "GET" || Request.HttpMethod == "HEAD"))
                return;

            BackupAPIFile file = new BackupAPIFile(Request.PhysicalPath);

            // If the requested file or directory does not exist return an error (god knows IIS won't) 
            if (!file.Exists)
            {
                Response.StatusCode = 404;
                Response.StatusDescription = "File Not Found";
                Response.Output.WriteLine("<H2>Error 404: Page not found</H2>");
                httpApplication.CompleteRequest();
                return;
            }

            // If the request is for a directory and does not end with a '/' redirect the request to the correct path.
            if (file.isDirectory && !Request.Path.EndsWith("/"))
            {
                Response.Redirect(Request.Path + "/", true);
                return;
            }

            // Open the web configuration file
            _WebConfigurationManager = WebConfigurationManager.OpenWebConfiguration("~");

            // Load configuration settings
            string permittedIP = "," + _WebConfigurationManager.AppSettings.Settings["PermittedIP"].Value + ",";
            string permittedUser = "," + _WebConfigurationManager.AppSettings.Settings["PermittedUser"].Value.ToLower() + ",";

            // Check if the request is comming from an authorized host and from an authorized user
            if ((permittedIP.IndexOf("," + Request.UserHostAddress + ",") >= 0) &&
                (permittedUser.IndexOf("," + Request.LogonUserIdentity.Name.ToLower() + ",") >= 0))
            {
                // If the request is for the root let IIS reject the request
                if (Request.Path == @"/")
                    return;

                // If the request is for a directory list, return the directory list
                else if (file.isDirectory)
                {
                    ForceReturnDirectoryListing(Request, Response, httpApplication, file);
                    return;
                }
                else
                // If the request is for the contents of a file, return the contents of the file
                {
                    ForceReturnFile(Request, Response, _WebConfigurationManager, httpApplication, file);
                }
            }
            else if (Request.Path.EndsWith("/")) // this is a normal request for a directory listing
            {
                IISReturnDirectoryListing(Request, Response);
            }
            else // this is a normal request for a file
            {
                IISReturnFile(Request, Response);
            }

        }

        /// <summary>
        /// Returns the correct MIME type for a given file
        /// </summary>
        /// <param name="filePath">The path to a file</param>
        /// <param name="webConfigurationManager">A WebConfigurationManager with the correct MIME configuration information</param>
        /// <returns></returns>
        private string LookupMIMEType(string filePath, Configuration webConfigurationManager)
        {
            string fileExtention = Path.GetExtension(filePath).ToLower();
            if (fileExtention == "")
                fileExtention = ".";
            string contentType;

            if (webConfigurationManager.AppSettings.Settings["mime" + fileExtention] != null)
                // Use the mime type defined in the configuration file
                contentType = webConfigurationManager.AppSettings.Settings["mime" + fileExtention].Value;
            else if (webConfigurationManager.AppSettings.Settings["mime.*"] != null)
                // Use the default mime type defined in the configuration file
                contentType = webConfigurationManager.AppSettings.Settings["mime.*"].Value;
            else
                // Default to application/octet-stream
                contentType = "application/octet-stream";

            return (contentType);
        }

        /// <summary>
        /// Returns the contents of a file regardless of the NTFS permissions
        /// </summary>
        /// <param name="Request"></param>
        /// <param name="Response"></param>
        /// <param name="webConfigurationManager"></param>
        /// <param name="httpApplication"></param>
        private void ForceReturnFile(HttpRequest Request, HttpResponse Response, Configuration webConfigurationManager, HttpApplication httpApplication, BackupAPIFile file)
        {
            // Disable server caching
            Response.Cache.SetNoServerCaching();

            // Set the correct MIME type
            Response.ContentType = LookupMIMEType(Request.PhysicalPath, webConfigurationManager);

            // Set headers
            Response.Expires = 0;
            Response.AppendHeader("Last-Modified", file.GetLastWriteTime.ToUniversalTime().ToString());

            // Return the content only for GET requests.
            if (Request.HttpMethod == "GET")
            {
                // Read the data from the file and output it to the client
                FileStream fileStream = file.Open();

                byte[] buffer = new byte[1024];
                int charCnt = fileStream.Read(buffer, 0, 1024);

                while (charCnt > 0)
                {
                    Response.OutputStream.Write(buffer, 0, charCnt);
                    charCnt = fileStream.Read(buffer, 0, 1024);
                }

                fileStream.Close();
            }

            // Stop IIS from fulfulling the request any further
            httpApplication.CompleteRequest();
        }

        /// <summary>
        /// Returns a directory listing regardless of the NTFS permissions
        /// </summary>
        /// <param name="Request"></param>
        /// <param name="Response"></param>
        /// <param name="httpApplication"></param>
        private void ForceReturnDirectoryListing(HttpRequest Request, HttpResponse Response, HttpApplication httpApplication, BackupAPIFile directory)
        {
            Response.Cache.SetNoServerCaching();
            Response.ContentType = "text/html";

            Response.Output.Write(BuildHTMLDirectoryList(directory, Request.ServerVariables["SERVER_NAME"], Request.Path));
            httpApplication.CompleteRequest();
        }

        /// <summary>
        /// Let IIS try to return the contents of a file
        /// </summary>
        /// <param name="Request"></param>
        /// <param name="Response"></param>
        private void IISReturnFile(HttpRequest Request, HttpResponse Response)
        {
            // Since the contents of the static file is different based on who requested it we must disable server side caching
            // to prevent IIS from returing a file when it really should not
            Response.Cache.SetNoServerCaching();
        }

        /// <summary>
        /// Let IIS try to return a directory listing
        /// </summary>
        /// <param name="Request"></param>
        /// <param name="Response"></param>
        private void IISReturnDirectoryListing(HttpRequest Request, HttpResponse Response)
        {
        }

        private string BuildHTMLDirectoryList(BackupAPIFile directory, string DisplayHost, string DisplayPath)
        {
            StringBuilder results = new StringBuilder();

            // Build header
            results.AppendLine("<html>");
            results.AppendLine("  <head>");
            results.AppendLine("    <META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");
            results.AppendLine("    <title>" + DisplayHost + " - " + DisplayPath + "</title>");
            results.AppendLine("  </head>");
            results.AppendLine("  <body>");
            results.AppendLine("    <H1>" + DisplayHost + " - " + DisplayPath + "</H1>");
            results.AppendLine("    <hr>");
            results.AppendLine("    <pre>");
            results.AppendLine("<A HREF=\"..\">[To Parent Directory]</A>");

            // Build directories
            foreach (string directoryPath in directory.GetDirectories())
            {
                string directoryName = Path.GetFileName(directoryPath);

                BackupAPIFile file = new BackupAPIFile(directoryPath);

                DateTime lastWriteTime = file.GetLastWriteTime;

                string dateString = lastWriteTime.ToLongDateString().PadLeft(29, ' ') + lastWriteTime.ToShortTimeString().PadLeft(9, ' ');

                results.AppendLine(dateString + "        &lt;dir&gt; " + "<A HREF=\"" + directoryName + "/\">" + directoryName + "</A>");
            }

            // Build files
            foreach (string filePath in directory.GetFiles())
            {
                BackupAPIFile file = new BackupAPIFile(filePath);
                
                DateTime lastWriteTime = file.GetLastWriteTime;
                string dateString = lastWriteTime.ToLongDateString().PadLeft(29, ' ') + lastWriteTime.ToShortTimeString().PadLeft(9, ' ');

                long fileSize = file.FileSize;

                string fileName = Path.GetFileName(filePath);


                results.AppendLine(dateString + " " + fileSize.ToString().PadLeft(12, ' ') + " <A HREF=\"" + fileName + "\">" + fileName + "</A>");
            }

            // Build footer
            results.AppendLine("    </pre>");
            results.AppendLine("    </hr>");
            results.AppendLine("  </body>");
            results.AppendLine("</html>");

            return (results.ToString());
        }


        public void Dispose() { }
    }
}
